Skip to content

feat: add profile avatar functionality to Laravel React starter kit #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

bennajah
Copy link

@bennajah bennajah commented Mar 6, 2025

feat: add profile avatar functionality to Laravel React starter kit

  • Added profile_photo_path column to users migration
  • Appended avatar to User model for profile photo handling
  • Implemented getAvatarAttribute() accessor to fetch user's profile photo URL
  • Enabled profile photo upload and deletion functionality

image

@haiffy420
Copy link

How did you manage to send multipart form data using the patch method?

I had to change the form method post and then add _method: 'patch' to the form data
profile.tsx

const { data, setData, post, errors, processing, recentlySuccessful } = useForm<Required<ProfileForm>>({
        name: auth.user.name,
        email: auth.user.email,
        avatar: null,
        _method: 'patch',
    });

const submit: FormEventHandler = (e) => {
        e.preventDefault();

        post(route('profile.update'), {
            preserveScroll: true,
        });
    };

Finally, to prevent changing the user's avatar to null if user didn't upload any file
ProfileController.php

public function update(ProfileUpdateRequest $request): RedirectResponse
    {
        $request->user()->fill($request->validated());

        if ($request->user()->isDirty('email')) {
            $request->user()->email_verified_at = null;
        }

        if ($request->avatar === null) {
            $request->user()->avatar = $request->user()->getOriginal('avatar');
        }

        if ($request->hasFile('avatar')) {
            if ($request->user()->avatar && Storage::exists($request->user()->avatar)) {
                Storage::delete($request->user()->avatar);
            }
            $request->user()->avatar = $request->file('avatar')->store('avatars', 'public');
        }

        $request->user()->save();

        return to_route('profile.edit');
    }

@bennajah
Copy link
Author

bennajah commented Mar 7, 2025

@haiffy420
fixed in last commit

@bennajah bennajah changed the title Add avatar field to user model and update profile handling feat: add profile avatar functionality to Laravel React starter kit Mar 7, 2025
@tnylea
Copy link
Contributor

tnylea commented Mar 7, 2025

@bennajah, looks very cool. I'll be testing this soon. Thanks!

@bennajah
Copy link
Author

bennajah commented Mar 7, 2025

@tnylea
I have also made a PR for the Vue Starter Kit: laravel/vue-starter-kit#79

@lewislarsen
Copy link

lewislarsen commented Mar 9, 2025

Would be awesome to see this, I often need avatars in my projects. (and sometimes gravatar doesn't suffice!)

@tnylea tnylea added the Additional Testing in Progress Needs more testing or waiting until we can add this feature to all starter kits label Mar 10, 2025
@tnylea
Copy link
Contributor

tnylea commented Mar 19, 2025

@bennajah Really appreciate the contribution and thanks for adding a test to this as well 👏

Before we merge this in, can you resolve the conflict in the profile? tsx? Also, could you update the getAvatarAttribute() to use the up-to-date way to use accessors here: https://laravel.com/docs/12.x/eloquent-mutators#accessors-and-mutators? After you get that updated, I'll approve this PR, and we can merge it.

Also, I would like to eventually add the ability for users to crop their photos similar to this:

image-crop.mp4

But that can come later ;) Ok, if you can make those updates we can get this puppy in there. Thanks again!

@tnylea tnylea added Awaiting User Response Waiting for developers response and removed Additional Testing in Progress Needs more testing or waiting until we can add this feature to all starter kits labels Mar 19, 2025
@bennajah
Copy link
Author

@tnylea The issue has been resolved! Additionally, I’ve implemented image cropping functionality using the react-image-crop library. It’s now fully integrated and working as expected. Let me know if you have any feedback or need further adjustments!

Recording.2025-03-20.012223.mp4

@tnylea
Copy link
Contributor

tnylea commented Mar 20, 2025

Excellent! Thanks @bennajah. Can you remove the composer.lock that's why the CI test is failing. I'll give this another review tomorrow.

Great job 👏

@bennajah
Copy link
Author

Excellent! Thanks @bennajah. Can you remove the composer.lock that's why the CI test is failing. I'll give this another review tomorrow.

Great job 👏

Done

@majweb
Copy link

majweb commented Mar 24, 2025

Will the crop functionality work in the starter kit?

@bennajah
Copy link
Author

Will the crop functionality work in the starter kit?

Currently only in react starter kit

@tnylea
Copy link
Contributor

tnylea commented Mar 31, 2025

Hey @bennajah, I want to give you a quick update. I am working on an additional feature for each starter kit for this sprint. I would like to include this along with that new feature; this way, when we announce the latest additions, Profile Photo upload will be one of those new features. I'll be sure to give you a shout out on X 😉

I will be adding the Vue and Livewire avatar cropping so that way, this feature is similar in each of the stacks 👍

@tnylea tnylea added Feature Parity in Progress We are currently working on implementing this in all the other starter kits and removed Awaiting User Response Waiting for developers response labels Mar 31, 2025
@majweb
Copy link

majweb commented Mar 31, 2025

When are you planning the first additions?

@ariefan
Copy link

ariefan commented Apr 9, 2025

I think the "Remove Photo" button shouldn't immediately delete the photo before the user clicks the "Save" button. This behavior may not align with user expectations, as users typically assume that all changes will only take effect after clicking "Save." Additionally, the remove action doesn't currently include any confirmation, which could lead to accidental deletions.

@tnylea
Copy link
Contributor

tnylea commented Apr 30, 2025

@bennajah, thanks for all your work on the Profile Photo upload feature. I've gone ahead and made a few refactors. I've separated the logic into its own profile-photo.tsx component that the profile.tsx uses.

I've also added some additional logic for when avatars of different sizes were being uploaded. The cropped area was causing some issues, but that's now been resolved.

Please let me know if you have any other changes you would like to see included in this PR. Other than that, this looks great. Appreciate it.

@bennajah
Copy link
Author

bennajah commented Apr 30, 2025

@bennajah, thanks for all your work on the Profile Photo upload feature. I've gone ahead and made a few refactors. I've separated the logic into its own profile-photo.tsx component that the profile.tsx uses.

I've also added some additional logic for when avatars of different sizes were being uploaded. The cropped area was causing some issues, but that's now been resolved.

Please let me know if you have any other changes you would like to see included in this PR. Other than that, this looks great. Appreciate it.

I think need to make profile photo disk to config file in config/app.php or separated profile.php file

I have added review

@OsitaDNU
Copy link

Issue with the profile update feature when using Laravel’s strict mode for models.

Hi @bennajah,

Firstly, thank you for adding this feature as it's something I missed from the Breeze starter kit. However, I ran into an issue while implementing it and wanted to share my findings.

In my project, I have enabled strict mode on my models, so the following method is active (see Laravel docs for more information):

Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());

With this enabled, Eloquent tries to assign the photo attribute to the database. Since the users table does not have a photo column, and silent discarding is prevented, the process fails with the following error:

SQLSTATE[42703]: Undefined column: 7 ERROR: column "photo" of relation "users" does not exist LINE 1: update "users" set "photo" = $1, "updated_at" = $2 where "id...

To resolve this, we need to remove photo from the validated data before filling the user model. Here’s the updated update method in the controller:

public function update(ProfileUpdateRequest $request): RedirectResponse
{
    $validated = $request->validated();

    // Remove 'photo' from validated data before filling the user model
    if ($request->hasFile('photo')) {
        $request->user()->updateProfilePhoto($request->validated('photo'));
        unset($validated['photo']);
    }

    $request->user()->fill($validated);

    if ($request->user()->isDirty('email')) {
        $request->user()->email_verified_at = null;
    }

    $request->user()->save();

    return to_route('profile.edit');
}

I recommend including this adjustment for anyone who may have enabled model strictness. Thanks again!

@bennajah
Copy link
Author

Issue with the profile update feature when using Laravel’s strict mode for models.

Hi @bennajah,

Firstly, thank you for adding this feature as it's something I missed from the Breeze starter kit. However, I ran into an issue while implementing it and wanted to share my findings.

In my project, I have enabled strict mode on my models, so the following method is active (see Laravel docs for more information):

Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());

With this enabled, Eloquent tries to assign the photo attribute to the database. Since the users table does not have a photo column, and silent discarding is prevented, the process fails with the following error:

SQLSTATE[42703]: Undefined column: 7 ERROR: column "photo" of relation "users" does not exist LINE 1: update "users" set "photo" = $1, "updated_at" = $2 where "id...

To resolve this, we need to remove photo from the validated data before filling the user model. Here’s the updated update method in the controller:

public function update(ProfileUpdateRequest $request): RedirectResponse
{
    $validated = $request->validated();

    // Remove 'photo' from validated data before filling the user model
    if ($request->hasFile('photo')) {
        $request->user()->updateProfilePhoto($request->validated('photo'));
        unset($validated['photo']);
    }

    $request->user()->fill($validated);

    if ($request->user()->isDirty('email')) {
        $request->user()->email_verified_at = null;
    }

    $request->user()->save();

    return to_route('profile.edit');
}

I recommend including this adjustment for anyone who may have enabled model strictness. Thanks again!

Try to add 'profile_photo_path' to fillable array in user model

@dpertsin
Copy link

@bennajah in my case I have to

  1. Add fillable property [photo] to allow mass assignment on [App\Models\User].
    After I add the photo at fillable on User model I get this:
    SQLSTATE[42S22]: Column not found: 1054 Unknown column 'photo' in 'field list'
  2. So I add on the migrations the photo too, and it is working.

@OsitaDNU
Copy link

@bennajah Adding profile_photo_path to the $fillable array on the User model doesn't fix the issue because your request uses photo. Unless you update profile_photo_path in migration to photo, this won’t align. Even if you do, I think you'll be performing an unnecessary operation by filling the photo column in app/Http/Controllers/Settings/ProfileController::update() and then immediately overwriting it with the correct path (including profile-photos/) in app/Traits/HasProfilePhoto::updateProfilePhoto().

@dpertsin Adding photo to $fillable and the user migration introduces a redundant column that isn’t actually used, even if it suppresses the error you encountered. See the attached screenshot.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature Parity in Progress We are currently working on implementing this in all the other starter kits
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants